/*
 * The MIT License
 *
 * Copyright (c) 2011 Takahiro Hashimoto
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package jp.a840.websocket.impl;

import jp.a840.websocket.HttpHeader;
import jp.a840.websocket.WebSocket;
import jp.a840.websocket.exception.ErrorCode;
import jp.a840.websocket.exception.WebSocketException;
import jp.a840.websocket.frame.Frame;
import jp.a840.websocket.frame.FrameParser;
import jp.a840.websocket.handler.PacketDumpStreamHandler;
import jp.a840.websocket.handler.SSLStreamHandler;
import jp.a840.websocket.handler.StreamHandlerAdapter;
import jp.a840.websocket.handler.StreamHandlerChain;
import jp.a840.websocket.handler.WebSocketHandler;
import jp.a840.websocket.handler.WebSocketPipeline;
import jp.a840.websocket.handler.WebSocketStreamHandler;
import jp.a840.websocket.handshake.Handshake;
import jp.a840.websocket.handshake.SSLHandshake;
import jp.a840.websocket.util.StringUtil;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Collection;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * The WebSocket base client.
 *
 * @author t-hashimoto
 */
public abstract class WebSocketBase implements WebSocket {

    /**
     * The location.
     */
    protected URI location;

    /**
     * the URL to which to connect.
     */
    protected String path;

    /**
     * The use ssl.
     */
    protected boolean useSsl = false;

    /**
     * The ssl handshake.
     */
    protected SSLHandshake sslHandshake;

    /**
     * endpoint.
     */
    protected InetSocketAddress endpointAddress;

    /**
     * connection timeout(second).
     */
    protected int connectionTimeout = 60 * 1000;

    /**
     * connection read timeout(second).
     */
    protected int connectionReadTimeout = 0;

    /**
     * blocking mode.
     */
    private boolean blockingMode = true;

    /**
     * The packet dump mode.
     */
    private static int packetDumpMode;

    /**
     * quit flag.
     */
    protected volatile boolean quit;

    /**
     * subprotocol name array.
     */
    protected String[] protocols;

    /**
     * The server protocols.
     */
    protected String[] serverProtocols;

    /**
     * The buffer size.
     */
    protected int bufferSize;

    /**
     * The upstream buffer.
     */
    protected ByteBuffer upstreamBuffer;

    /**
     * The downstream buffer.
     */
    protected ByteBuffer downstreamBuffer;

    /**
     * The origin.
     */
    protected String origin;

    /**
     * The upstream queue.
     */
    protected BlockingQueue<ByteBuffer> upstreamQueue;

    /**
     * websocket handler.
     */
    protected WebSocketHandler handler;

    /**
     * The pipeline.
     */
    protected WebSocketPipeline pipeline;

    /**
     * The socket.
     */
    protected SocketChannel socket;

    /**
     * The selector.
     */
    protected Selector selector;

    /**
     * The handshake.
     */
    private Handshake handshake;

    /**
     * The frame parser.
     */
    private FrameParser frameParser;

    /**
     * The response header map.
     */
    protected HttpHeader responseHeader;

    /**
     * The request header map.
     */
    protected HttpHeader requestHeader;

    /**
     * The response status.
     */
    protected int responseStatus;

    /**
     * The state.
     */
    protected volatile State state = State.CLOSED;

    /**
     * The close latch.
     */
    protected CountDownLatch closeLatch;

    /**
     * The handshake latch.
     */
    protected CountDownLatch handshakeLatch;

    /**
     * worker.
     */
    protected ExecutorService executorService;

    private AtomicInteger executorThreadNumber = new AtomicInteger(0);

    private String executorThreadName;

    /**
     * Instantiates a new web socket base.
     *
     * @param url       the url
     * @param handler   the handler
     * @param protocols the protocols
     * @throws WebSocketException the web socket exception
     */
    public WebSocketBase(String url, String origin, WebSocketHandler handler,
                         String... protocols) throws WebSocketException {
        this.origin = origin;
        this.protocols = protocols;
        this.handler = handler;

        init(url);
    }

    /**
     * Instantiates a new web socket base.
     *
     * @param url       the url
     * @param handler   the handler
     * @param protocols the protocols
     * @throws WebSocketException the web socket exception
     */
    public WebSocketBase(String url, WebSocketHandler handler,
                         String... protocols) throws WebSocketException {
        this.protocols = protocols;
        this.handler = handler;

        init(url);

        this.origin = this.location.getHost() + (this.location.getPort() > 0 ? ":" + this.location.getPort() : "");
    }

    /**
     * Inits the.
     *
     * @param url the url
     * @throws WebSocketException the web socket exception
     */
    protected void init(String url) throws WebSocketException {
        // init properties
        initializeProperties();
        parseUrl(url);
        // parse url
        initializePipeline();
    }

    /**
     * Initialize properties.
     *
     * @throws WebSocketException the web socket exception
     */
    protected void initializeProperties() throws WebSocketException {
        this.bufferSize = Integer.getInteger("websocket.bufferSize", 0x7FFF);
        int upstreamQueueSize = Integer.getInteger("websocket.upstreamQueueSize", 500);
        this.upstreamQueue = new LinkedBlockingQueue<ByteBuffer>(upstreamQueueSize);
        this.downstreamBuffer = ByteBuffer.allocate(this.bufferSize);
        this.upstreamBuffer = ByteBuffer.allocate(this.bufferSize);
        this.packetDumpMode = Integer.getInteger("websocket.packatdump", 0);
        this.requestHeader = new HttpHeader();
    }

    /**
     * Initialize pipeline.
     *
     * @throws WebSocketException the web socket exception
     */
    protected void initializePipeline() throws WebSocketException {
        // setup pipeline
        this.pipeline = new WebSocketPipeline();

        // Add upstream qeueue handler first.
        // it push the upstream buffer to a sendqueue and then wakeup a selector
        this.pipeline.addStreamHandler(new StreamHandlerAdapter() {
            public void nextUpstreamHandler(WebSocket ws, ByteBuffer buffer,
                                            Frame frame, StreamHandlerChain chain) throws WebSocketException {
                try {
                    upstreamQueue.put(buffer);
                    socket.register(selector, SelectionKey.OP_WRITE);
                } catch (InterruptedException e) {
                    throw new WebSocketException(ErrorCode.E3030, e);
                } catch (ClosedChannelException e) {
                    throw new WebSocketException(ErrorCode.E3020, e);
                }
            }

            public void nextHandshakeUpstreamHandler(WebSocket ws, ByteBuffer buffer,
                                                     StreamHandlerChain chain) throws WebSocketException {
                try {
                    upstreamQueue.put(buffer);
                    socket.register(selector, SelectionKey.OP_WRITE);
                } catch (InterruptedException e) {
                    throw new WebSocketException(ErrorCode.E3031, e);
                } catch (ClosedChannelException e) {
                    throw new WebSocketException(ErrorCode.E3020, e);
                }
            }
        });

        if (this.useSsl) {
            this.sslHandshake = new SSLHandshake(this.endpointAddress);
            this.pipeline.addStreamHandler(new PacketDumpStreamHandler());
            this.pipeline.addStreamHandler(new SSLStreamHandler(this.sslHandshake, this.bufferSize));
        }

        // orverriding initilize method by subclass
        initializePipeline(pipeline);
    }

    /**
     * Initialize pipeline.
     *
     * @param pipeline the pipeline
     * @throws WebSocketException the web socket exception
     */
    protected void initializePipeline(WebSocketPipeline pipeline) throws WebSocketException {
        // for debug
        this.pipeline.addStreamHandler(new PacketDumpStreamHandler());
        this.pipeline.addStreamHandler(new WebSocketStreamHandler(getHandshake(), getFrameParser()));
    }

    /**
     * Parses the url.
     *
     * @param urlStr the url str
     * @throws WebSocketException the web socket exception
     */
    private void parseUrl(String urlStr) throws WebSocketException {
        try {
            URI uri = new URI(urlStr);
            if (!(uri.getScheme().equals("ws") || uri.getScheme().equals("wss"))) {
                throw new WebSocketException(ErrorCode.E3007, uri.toString());
            }
            if (uri.getScheme().equals("wss")) {
                useSsl = true;
            }
            path = (uri.getPath().equals("") ? "/" : uri.getPath()) + (uri.getQuery() != null ? "?" + uri.getQuery() : "");
            int port = uri.getPort();
            if (port < 0) {
                if (uri.getScheme().equals("ws")) {
                    port = 80;
                } else if (uri.getScheme().equals("wss")) {
                    port = 443;
                    useSsl = true;
                } else {
                    throw new WebSocketException(ErrorCode.E3008, uri.toString());
                }
            }
            endpointAddress = new InetSocketAddress(uri.getHost(), port);
            location = uri;
        } catch (URISyntaxException e) {
            throw new WebSocketException(ErrorCode.E3009, e);
        }
    }

    @Override
    public URI getLocation() {
        return location;
    }

    @Override
    public void send(Frame frame) throws WebSocketException {
        if (!isConnected()) {
            throw new WebSocketException(ErrorCode.E3010);
        }
        pipeline.sendUpstream(this, null, frame);
    }

    /**
     * Send.
     *
     * @param obj the obj
     * @throws WebSocketException the web socket exception
     */
    public void send(Object obj) throws WebSocketException {
        send(createFrame(obj));
    }

    public void send(ByteBuffer buffer) throws WebSocketException {
        send(createFrame(buffer));
    }

    public void send(byte[] bytes) throws WebSocketException {
        send(createFrame(bytes));
    }

    public void send(String str) throws WebSocketException {
        send(createFrame(str));
    }

    /**
     * <pre>
     * CONNECTED -> HANDSHAKE, CLOSED
     * HANDSHAKE -> WAIT, CLOSED
     * WAIT -> WAIT, CLOSED
     * CLOSED -> CONNECTED, CLOSED
     * </pre>.
     *
     * @author Takahiro Hashimoto
     */
    enum State {

        /**
         * The CONNECTED.
         */
        CONNECTED,
        /**
         * The HANDSHAKE.
         */
        HANDSHAKE,
        /**
         * The WAIT.
         */
        WAIT,
        /**
         * The CLOSING.
         */
        CLOSING,
        /**
         * The CLOSED.
         */
        CLOSED;

        /**
         * The state map.
         */
        private static EnumMap<State, EnumSet<State>> stateMap = new EnumMap<State, EnumSet<State>>(
                State.class);

        static {
            stateMap.put(CONNECTED, EnumSet.of(State.HANDSHAKE, State.CLOSED));
            stateMap.put(HANDSHAKE, EnumSet.of(State.WAIT, State.CLOSED));
            stateMap.put(WAIT, EnumSet.of(State.WAIT, State.CLOSING, State.CLOSED));
            stateMap.put(CLOSING, EnumSet.of(State.CLOSED));
            stateMap.put(CLOSED, EnumSet.of(State.CONNECTED, State.CLOSED));
        }

        /**
         * Can transition to.
         *
         * @param state the state
         * @return true, if successful
         */
        boolean canTransitionTo(State state) {
            EnumSet<State> set = stateMap.get(this);
            if (set == null)
                return false;
            return set.contains(state);
        }

        /**
         * Checks if is connected.
         *
         * @return true, if is connected
         */
        boolean isConnected() {
            switch (this) {
                case CONNECTED:
                case HANDSHAKE:
                case WAIT:
                    return true;
            }
            return false;
        }
    }

    /**
     * Transition to.
     *
     * @param to the to
     * @return the state
     */
    protected State transitionTo(State to) {
        if (state.canTransitionTo(to)) {
            State old = state;
            state = to;
            return old;
        } else {
            throw new IllegalStateException("Couldn't transtion from " + state
                    + " to " + to);
        }
    }

    /**
     * State.
     *
     * @return the state
     */
    protected State state() {
        return state;
    }

    /**
     * Read.
     *
     * @param socket the socket
     * @param buffer the buffer
     * @throws WebSocketException the web socket exception
     */
    protected void read(SocketChannel socket, ByteBuffer buffer)
            throws WebSocketException {
        try {
            buffer.clear();
            if (socket.read(buffer) < 0) {
                throw new WebSocketException(ErrorCode.E3020);
            }
            buffer.flip();
        } catch (IOException ioe) {
            throw new WebSocketException(ErrorCode.E3021, ioe);
        }
    }

    /**
     * Creates the socket.
     *
     * @return the socket channel
     * @throws IOException Signals that an I/O exception has occurred.
     */
    protected SocketChannel createSocket() throws IOException {
        SocketChannel socket = SocketChannel.open();
        socket.configureBlocking(false);
        return socket;
    }

    @Override
    public void connect() throws WebSocketException {
        try {
            // check connection status
            if (isConnected()) {
                throw new WebSocketException(ErrorCode.E3039);
            }

            if (!state.canTransitionTo(State.CONNECTED)) {
                throw new WebSocketException(ErrorCode.E3040, state.name());
            }

            // initialize connection
            handshakeLatch = new CountDownLatch(1);
            closeLatch = new CountDownLatch(1);

            socket = SocketChannel.open();
            socket.configureBlocking(false);
            selector = Selector.open();
            socket.register(selector, SelectionKey.OP_READ);

            long start = System.currentTimeMillis();
            InetSocketAddress remoteAddress = this.endpointAddress;

            // start connect to remote address
            if (socket.connect(remoteAddress)) {
                throw new WebSocketException(ErrorCode.E3041);
            }
            while (!socket.finishConnect()) {
                if ((System.currentTimeMillis() - start) > connectionTimeout) {
                    throw new WebSocketException(ErrorCode.E3042);
                }
            }
            // connect done
            // try handshakes
            transitionTo(State.CONNECTED);

            if (useSsl) {
                sslHandshake.doHandshake(socket);
            }
            pipeline.sendHandshakeUpstream(this, null); // send handshake request

            transitionTo(State.HANDSHAKE);

            Runnable worker = new Runnable() {
                public void run() {
                    try {
                        while (!quit) {
                            selector.select(100);
                            for (SelectionKey key : selector.selectedKeys()) {
                                if (key.isValid() && key.isWritable() && upstreamQueue.peek() != null) {
                                    SocketChannel channel = (SocketChannel) key.channel();
                                    channel.write(upstreamQueue.poll());
                                    socket.register(selector, SelectionKey.OP_READ);
                                } else if (key.isValid() && key.isReadable()) {
                                    read(socket, downstreamBuffer); // read
                                    // response
                                    switch (state) {
                                        case HANDSHAKE: // CONNECTED -> HANDSHAKE
                                            pipeline.sendHandshakeDownstream(WebSocketBase.this, downstreamBuffer);
                                            if (getHandshake().isDone()) {
                                                processBuffer(downstreamBuffer);
                                                handshakeLatch.countDown();
                                            }
                                            break;
                                        case WAIT: // read frames
                                        case CLOSING:
                                            processBuffer(downstreamBuffer);
                                            break;
                                        default:
                                            // ignore
                                    }
                                }
                            }
                            if (!upstreamQueue.isEmpty()) {
                                if (state != State.CLOSED) {
                                    socket.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                                } else {
                                    socket.register(selector, SelectionKey.OP_WRITE);
                                }
                            } else {
                                if (state == State.CLOSED) {
                                    quit = true;
                                }
                            }
                        }
                    } catch (WebSocketException we) {
                        handler.onError(WebSocketBase.this, we);
                    } catch (Exception e) {
                        handler.onError(WebSocketBase.this,
                                new WebSocketException(ErrorCode.E3043, e));
                    } finally {
                        try {
                            socket.close();
                        } catch (IOException e) {
                            // ignore
                        }
                        try {
                            selector.close();
                        } catch (IOException e) {
                            // ignore
                        }
                        handler.onClose(WebSocketBase.this);
                        handshakeLatch.countDown();
                        closeLatch.countDown();
                        synchronized (this) {
                            if (executorService != null) {
                                executorService.shutdown();
                            }
                        }
                    }
                }
            };

            quit = false;
            if (blockingMode) {
                worker.run();
            } else {
                this.executorThreadName = "WebSocket-Executor-" + executorThreadNumber.incrementAndGet();
                executorService = Executors
                        .newSingleThreadExecutor(new ThreadFactory() {
                            public Thread newThread(Runnable r) {
                                Thread t = new Thread(r, executorThreadName);
                                t.setDaemon(true);
                                return t;
                            }
                        });
                executorService.submit(worker);
                executorService.shutdown();
                handshakeLatch.await();
            }

        } catch (WebSocketException we) {
            handler.onError(this, we);
        } catch (Exception e) {
            handler.onError(this, new WebSocketException(ErrorCode.E3044, e));
        }
    }

    /**
     * Process buffer.
     *
     * @param buffer the buffer
     * @throws WebSocketException the web socket exception
     */
    protected void processBuffer(ByteBuffer buffer) throws WebSocketException {
        while (buffer.hasRemaining()) {
            pipeline.sendDownstream(this, buffer, null);
        }
    }

    @Override
    public boolean isConnected() {
        return state.isConnected();
    }

    @Override
    public void close() {
        try {
            if (state == State.WAIT) {
                closeWebSocket();
                if (!Thread.currentThread().getName().equals(this.executorThreadName)) {
                    try {
                        closeLatch.await(30, TimeUnit.SECONDS);
                    } catch (InterruptedException e) {
                        // ignore
                    }
                    if (executorService != null) {
                        try {
                            executorService.shutdown();
                            executorService.awaitTermination(30, TimeUnit.SECONDS);
                        } catch (InterruptedException e) {
                            // ignore
                        } finally {
                            synchronized (this) {
                                executorService = null;
                            }
                        }
                    }
                }
            }
        } catch (WebSocketException e) {
            handler.onError(this, e);
        }
    }

    /**
     * Await termination.
     *
     * @param timeout the timeout
     * @param unit    the unit
     * @throws InterruptedException the interrupted exception
     */
    protected void awaitTermination(int timeout, TimeUnit unit) throws InterruptedException {
        if (executorService != null) {
            executorService.awaitTermination(timeout, unit);
        }
    }

    public void awaitClose() throws InterruptedException {
        closeLatch.await();
    }

    /**
     * Close web socket.
     *
     * @throws WebSocketException the web socket exception
     */
    protected void closeWebSocket() throws WebSocketException {
        quit = true;
        transitionTo(State.CLOSED);
    }

    public Frame createFrame(Object obj) throws WebSocketException {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(obj);

            byte[] bodyData = baos.toByteArray();
            return createFrame(bodyData);
        } catch (Exception e) {
            throw new WebSocketException(ErrorCode.E3550, e);
        }
    }

    public Frame createFrame(ByteBuffer buffer) throws WebSocketException {
        byte[] bytes = new byte[buffer.limit()];
        buffer.get(bytes);
        return createFrame(bytes);
    }

    /**
     * Join.
     *
     * @param delim       the delim
     * @param collections the collections
     * @return the string
     */
    protected static String join(String delim, Collection<String> collections) {
        return StringUtil.join(delim, collections);
    }

    /**
     * Join.
     *
     * @param delim   the delim
     * @param strings the strings
     * @return the string
     */
    protected static String join(String delim, String... strings) {
        return StringUtil.join(delim, strings);
    }

    /**
     * Join.
     *
     * @param delim   the delim
     * @param start   the start
     * @param end     the end
     * @param strings the strings
     * @return the string
     */
    protected static String join(String delim, int start, int end,
                                 String... strings) {
        return StringUtil.join(delim, start, end, strings);
    }

    /**
     * Adds the header.
     *
     * @param sb    the sb
     * @param key   the key
     * @param value the value
     */
    protected static void addHeader(StringBuilder sb, String key, String value) {
        StringUtil.addHeader(sb, key, value);
    }

    /**
     * Gets the web socket version.
     *
     * @return the web socket version
     */
    protected abstract int getWebSocketVersion();

    /**
     * New handshake instance.
     *
     * @return the handshake
     */
    protected abstract Handshake newHandshakeInstance();

    /**
     * Gets the handshake.
     *
     * @return the handshake
     */
    protected synchronized Handshake getHandshake() {
        if (handshake == null) {
            handshake = newHandshakeInstance();
        }
        return handshake;
    }

    /**
     * New frame parser instance.
     *
     * @return the frame parser
     */
    protected abstract FrameParser newFrameParserInstance();

    /**
     * Gets the frame parser.
     *
     * @return the frame parser
     */
    protected synchronized FrameParser getFrameParser() {
        if (frameParser == null) {
            frameParser = newFrameParserInstance();
        }
        return frameParser;
    }

    /* (non-Javadoc)
     * @see jp.a840.websocket.WebSocket#isBlockingMode()
     */
    public boolean isBlockingMode() {
        return blockingMode;
    }

    /* (non-Javadoc)
     * @see jp.a840.websocket.WebSocket#setBlockingMode(boolean)
     */
    public void setBlockingMode(boolean blockingMode) {
        this.blockingMode = blockingMode;
    }

    /**
     * Gets the server protocols.
     *
     * @return the server protocols
     */
    public String[] getServerProtocols() {
        return serverProtocols;
    }

    /**
     * Sets the server protocols.
     *
     * @param serverProtocols the new server protocols
     */
    public void setServerProtocols(String[] serverProtocols) {
        this.serverProtocols = serverProtocols;
    }

    /**
     * Gets the path.
     *
     * @return the path
     */
    public String getPath() {
        return path;
    }

    /* (non-Javadoc)
     * @see jp.a840.websocket.WebSocket#getEndpoint()
     */
    public InetSocketAddress getEndpoint() {
        return endpointAddress;
    }

    /**
     * Gets the protocols.
     *
     * @return the protocols
     */
    public String[] getProtocols() {
        return protocols;
    }

    /**
     * Gets the origin.
     *
     * @return the origin
     */
    public String getOrigin() {
        return origin;
    }

    /**
     * Gets the response header.
     *
     * @return the response header
     */
    public HttpHeader getResponseHeader() {
        return responseHeader;
    }

    /**
     * Gets the request header.
     *
     * @return the request header
     */
    public HttpHeader getRequestHeader() {
        return requestHeader;
    }

    /**
     * Gets the response status.
     *
     * @return the response status
     */
    public int getResponseStatus() {
        return responseStatus;
    }

    @Override
    public int getConnectionTimeout() {
        return connectionTimeout;
    }

    @Override
    public void setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout * 1000;
    }

    /**
     * Gets the connection read timeout.
     *
     * @return the connection read timeout
     */
    public int getConnectionReadTimeout() {
        return connectionReadTimeout;
    }

    /**
     * Sets the connection read timeout.
     *
     * @param connectionReadTimeout the new connection read timeout
     */
    public void setConnectionReadTimeout(int connectionReadTimeout) {
        this.connectionReadTimeout = connectionReadTimeout * 1000;
    }

    /**
     * Sets the origin.
     *
     * @param origin the new origin
     */
    public void setOrigin(String origin) {
        this.origin = origin;
    }

    @Override
    public int getBufferSize() {
        return bufferSize;
    }

    /**
     * Gets the packet dump mode.
     *
     * @return the packet dump mode
     */
    public static int getPacketDumpMode() {
        return packetDumpMode;
    }

    /**
     * Sets the packet dump mode.
     *
     * @param packetDumpMode the new packet dump mode
     */
    public static void setPacketDumpMode(int packetDumpMode) {
        WebSocketBase.packetDumpMode = packetDumpMode;
    }

}
